Unity マイコン間のシリアル通信(送受信)
※この内容は旧)Unity マイコン間のシリアル通信(送受信)(※UniRXを利用)を2025年現在にあわせて更新したものです。
1. はじめに
※シリアル通信に関する導入の説明はこちら→ 各種シリアル通信のまとめ
ローカル環境の複数の機器間でデータをやりとりしたい。そんなとき、もっともシンプルな解決方法の一つがシリアル通信です。このページでは、Unity マイコン間のシリアル通信のやり方を説明します。かつては、UniRXを利用た非同期シリアル通信を解説していましたが、2025年現在では(というか、ずいぶん前から)、Unityの標準的なC#で対応できるようになっているため、内容を大幅に更新しました。
解説するのは下記の内容です。
Unity から Arduino(M5 Atom Lite)とシリアル通信を行い、キー入力でコマンドを送信
受信したデータに応じて Arduino 側が LED を制御
双方向通信の基本を学ぶ
2. サンプルコードの概要
2.1 サンプルコードでできること
https://gyazo.com/21ad9d1c840f2ef9999f2ad3c451d7fc
Unity から Arduino(M5 Atom Lite)とシリアル通信
Unity上でキー(←、↑、→、Space)を入力すると、Atom LiteのLEDの色が変化
2.2 環境構成
table:環境構成
項目 内容
OS Windows 10/11
Unity 2022.3.61f1(※この内容はUnity 2022以降に合わせています)
Arduino IDE 最新版(M5Atom環境設定済み)
デバイス M5 Atom Lite(または Arduino Uno 等)
通信方式 USBシリアル(115200bps)
※この内容はUnity 2022以降に合わせています。
3 セットアップの手順
3.1 M5 Atom Lite側の設定
下記のサンプルコードを書き込んでおく。
※Atom Liteの使い方については → M5Atomの使い方
M5 Atom Lite(Arduino) 側のサンプルコード
送信:Atom Lite のボタンを押すと 0~3 を送信
受信:Unity から送信された文字に応じてとLEDが変化
code: M5Atomlite_Serial_LED
#include <M5Atom.h>
static int sendState = 0;
void setup() {
M5.begin(true, false, true);
Serial.begin(115200);
M5.dis.setBrightness(40);
M5.dis.drawpix(0, CRGB::Black);
}
void loop() {
M5.update();
if (Serial.available()) {
char c = Serial.read();
switch (c) {
case '0': M5.dis.drawpix(0, CRGB::Black); break;
case '1': M5.dis.drawpix(0, CRGB::Green); break;
case '2': M5.dis.drawpix(0, CRGB::Red); break;
case '3': M5.dis.drawpix(0, CRGB::Blue); break;
}
}
if (M5.Btn.wasPressed()) {
sendState = (sendState + 1) % 4;
Serial.println(sendState);
}
}
3.2 Unitry側の設定
Step 1 新規プロジェクトの作成
Unity Hub を起動 → New project をクリック
Editor version は 2022.x以降のものを指定
テンプレートは 3D を選択
プロジェクト名と保存先を決めて 「作成」
Step 2 Player 設定(.NET関連)  【重要!】
Edit → Project Settings… → Player Settings… → Other Settings
https://gyazo.com/6cdc50718aed56a288ceea29b96771a5
Configurationの項目で下記を設定
Api Compatibility Level: .NET Framework
https://gyazo.com/35357e2fe32a0d5356f7f454aeb31953
この手順を抜かすとシリアル通信の際にSystem.IO.Portsに関連するエラーが出ます。
Step 3 シリアル通信スクリプトの作成
Project ウィンドウで右クリック → Create → Folder → Scripts を作成。
https://gyazo.com/36bcbbeb5498e27ea4ed27b0f1ae522e
Scripts 内で右クリック → Create → C# Script → 名前を AtomLiteSerial に
AtomLiteSerial.csをダブルクリックで開き、下記のサンプルコードに差し替え
code: AtomLiteSerial.cs
// ファイル名 AtomLiteSerial.cs
// ファイル名とクラス名をあわせる必要があるので注意
using System;
using System.Collections.Concurrent;
using System.IO.Ports;
using System.Threading;
using UnityEngine;
public class AtomLiteSerial : MonoBehaviour
{
Header("Serial Settings")
SerializeField private string portName = "COM3"; // macOS例: "/dev/tty.usbmodem1101"
SerializeField private int baudRate = 115200;
SerializeField private bool dtrEnable = false; // Arduino/ESP32の自動リセット抑制したい場合はfalse推奨
SerializeField private bool rtsEnable = false;
Header("Key → Char Mapping")
SerializeField private KeyCode keyLeft = KeyCode.LeftArrow; // 送信: '1'
SerializeField private KeyCode keyUp = KeyCode.UpArrow; // 送信: '2'
SerializeField private KeyCode keyRight= KeyCode.RightArrow; // 送信: '3'
SerializeField private KeyCode keySpace= KeyCode.Space; // 送信: '0'
private SerialPort _sp;
private Thread _readThread;
private volatile bool _running = false;
private readonly ConcurrentQueue<string> _recvQueue = new ConcurrentQueue<string>();
void Start()
{
try
{
_sp = new SerialPort(portName, baudRate)
{
NewLine = "\n",
ReadTimeout = 50, // 読み取りタイムアウト(ms)
WriteTimeout = 50,
DtrEnable = dtrEnable,
RtsEnable = rtsEnable,
};
_sp.Open();
// ESP32系はポートOpen時にリセットがかかることがあるので、少し待ってから通信開始
StartCoroutine(DelayStartRead(1000));
Debug.Log($"Serial Opened {portName} @ {baudRate}");
}
catch (Exception ex)
{
Debug.LogError($"Serial Open failed: {ex.Message}");
}
}
System.Collections.IEnumerator DelayStartRead(int ms)
{
yield return new WaitForSeconds(ms / 1000f);
StartReadThread();
}
void StartReadThread()
{
if (_sp == null || !_sp.IsOpen) return;
_running = true;
_readThread = new Thread(ReadLoop);
_readThread.IsBackground = true;
_readThread.Start();
}
void ReadLoop()
{
while (_running)
{
try
{
// 1バイトずつ読み取り(来ていなければTimeoutで抜ける)
int b = _sp.ReadByte();
if (b >= 0)
{
char c = (char)b;
_recvQueue.Enqueue(c.ToString());
}
}
catch (TimeoutException)
{
// 無通信時はここに来る。ループ継続
}
catch (Exception ex)
{
Debug.LogWarning($"Serial Read error: {ex.Message}");
Thread.Sleep(50);
}
}
}
void Update()
{
// --- キー入力 → 文字送信 ---
if (Input.GetKeyDown(keyLeft)) SendChar('1'); // 左 : '1'
if (Input.GetKeyDown(keyUp)) SendChar('2'); // 上 : '2'
if (Input.GetKeyDown(keyRight)) SendChar('3'); // 右 : '3'
if (Input.GetKeyDown(keySpace)) SendChar('0'); // Space: '0'
// --- 受信キューをメインスレッドでログ表示 ---
while (_recvQueue.TryDequeue(out var msg))
{
// 改行や余分な制御文字は適宜整形
var trimmed = msg.Replace("\r", "").Replace("\n", "");
if (!string.IsNullOrEmpty(trimmed))
{
Debug.Log($"Serial RX {trimmed}");
}
}
}
private void SendChar(char ch)
{
try
{
if (_sp != null && _sp.IsOpen)
{
// 単一文字だけ送る(改行なし)
_sp.Write(new[] { ch }, 0, 1);
Debug.Log($"Serial TX {ch}");
}
}
catch (Exception ex)
{
Debug.LogWarning($"Serial Write error: {ex.Message}");
}
}
void OnApplicationQuit()
{
_running = false;
try
{
if (_readThread != null && _readThread.IsAlive)
{
_readThread.Join(200);
_readThread = null;
}
}
catch { /* ignore */ }
try
{
if (_sp != null)
{
if (_sp.IsOpen) _sp.Close();
_sp.Dispose();
}
}
catch { /* ignore */ }
}
}
送信:キー(左、右、下、スペース)を押すと設定した文字を送信します。
左 → 「1」を送信
上 →「2」を送信
右 → 「3」を送信
スペース → 「0」を送信
受信:受信した文字をログに表示します。
Step 4 空のオブジェクトにAtomLiteSerial.csをアタッチして、シーンに組み込む
Hierarchyウィンドウで右クリック→「Create Emputy」で空のオブジェクトを作成→名前を 「SerialManager」に
https://gyazo.com/fab90a372fa9698b2ca4ba1b4e0a1514
SerialManagerにAtomLiteSerial をドラッグ&ドロップしてAtomLiteSerialをアタッチ
https://gyazo.com/5c9f24d72b5cb45a65c7d067fe117cd9
SerialManagerをクリックしてInspector のPort Name に デバイスマネージャで確認した COMx を入力(例:COM3)
https://gyazo.com/aa97b69e29413fc44a93cfce52805bc8
Step 5 シーンを保存
Hierarchyウィンドウの「SampleScene」右クリックし→「Save Scene」でシーンを保存
4. 実行テスト
Arduino の シリアルモニタを閉じていることを確認
Unity でシーンを開き、Play。Game ビューをクリック
キー入力:
← → '1'を送信 (緑・点滅→点灯)
↑ → '2'を送信 (赤・点滅→点灯)
→ → '3を送信' (青・点滅→点灯)
Space → '0' (消灯)
M5 のボタンを押すと、0/1/2/3 が Console に表示。
5. 既存プロジェクトへの組み込み
「Player Settings」を「.NET Framework」に  (3.2 Step 2 Player 設定)
AtomLiteSerial.cs を任意のシーンの GameObject にアタッチ。
参考
【研究室セミナー】超スマート社会をつくるVRxIoTセミナ「実世界を仮想化するフィジカル・コンピューティング入門」【2018・2019】
旧)Unity マイコン間のシリアル通信(送受信)(※UniRXを利用)
#シリアル通信
https://gyazo.com/71c7de59f100448c29cdb7f29fbd171b
Communication Robotics Lab.